package org.fle4y.app.server.database;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Hashtable;
import org.fle4y.common.utils.basic.ByteArray;
import org.fle4y.common.utils.basic.StringUtil;

/**
 * author : xiehui<br>
 * company : northking <br>
 * Create at: 2012-6-19<br>
 * 
 * @version : 1.0<br>
 *          mailto:flexie@foxmail.com<br>
 *          Description : 字段
 */
public class Column {
	private static Hashtable<String, Integer> types;
	final static int BIT = Types.BIT;
	final static int TINYINT = Types.TINYINT;
	final static int BIGINT = Types.BIGINT;
	final static int LONGVARBINARY = Types.LONGVARBINARY;
	final static int VARBINARY = Types.VARBINARY;
	final static int BINARY = Types.BINARY;
	final static int LONGVARCHAR = Types.LONGVARCHAR;
	final static int CHAR = Types.CHAR;
	final static int NUMERIC = Types.NUMERIC;
	final static int DECIMAL = Types.DECIMAL;
	final static int INTEGER = Types.INTEGER;
	final static int SMALLINT = Types.SMALLINT;
	final static int FLOAT = Types.FLOAT;
	final static int REAL = Types.REAL;
	final static int DOUBLE = Types.DOUBLE;
	final static int VARCHAR = Types.VARCHAR;
	final static int DATE = Types.DATE;
	final static int TIME = Types.TIME;
	final static int TIMESTAMP = Types.TIMESTAMP;
	final static int OTHER = Types.OTHER;
	final static int NULL = Types.NULL;
	final static int VARCHAR_IGNORECASE = 100;

	final static int TYPES[] = { BIT, TINYINT, BIGINT, LONGVARBINARY,
			VARBINARY, BINARY, LONGVARCHAR, CHAR, NUMERIC, DECIMAL, INTEGER,
			SMALLINT, FLOAT, REAL, DOUBLE, VARCHAR, DATE, TIME, TIMESTAMP,
			OTHER };
	String name;
	int type;
	private boolean nullable;
	static {
		types = new Hashtable<String, Integer>();
		addTypes(INTEGER, "INTEGER", "int", "java.lang.Integer");
		addType(INTEGER, "INT");
		addTypes(DOUBLE, "DOUBLE", "double", "java.lang.Double");
		addType(FLOAT, "FLOAT");
		addTypes(VARCHAR, "VARCHAR", "java.lang.String", null);
		addTypes(CHAR, "CHAR", "CHARACTER", null);
		addType(LONGVARCHAR, "LONGVARCHAR");
		addType(VARCHAR_IGNORECASE, "VARCHAR_IGNORECASE");
		addTypes(DATE, "DATE", "java.sql.Date", null);
		addTypes(TIME, "TIME", "java.sql.Time", null);
		addTypes(TIMESTAMP, "TIMESTAMP", "java.sql.Timestamp", "DATETIME");
		addTypes(DECIMAL, "DECIMAL", "java.math.BigDecimal", null);
		addType(NUMERIC, "NUMERIC");
		addTypes(BIT, "BIT", "java.lang.Boolean", "boolean");
		addTypes(TINYINT, "TINYINT", "java.lang.Short", "short");
		addType(SMALLINT, "SMALLINT");
		addTypes(BIGINT, "BIGINT", "java.lang.Long", "long");
		addTypes(REAL, "REAL", "java.lang.Float", "float");
		addTypes(BINARY, "BINARY", "byte[]", null);
		addType(VARBINARY, "VARBINARY");
		addType(LONGVARBINARY, "LONGVARBINARY");
		addTypes(OTHER, "OTHER", "java.lang.Object", "OBJECT");
	}

	private static void addTypes(int type, String name, String n2, String n3) {
		addType(type, name);
		addType(type, n2);
		addType(type, n3);
	}

	private static void addType(int type, String name) {
		if (name != null) {
			types.put(name, new Integer(type));
		}
	}
    static int getSize(Object data[], Table t) {
    	int l = data.length;
    	int type[] = new int[l];

    	for (int i = 0; i < l; i++) {
    	    type[i] = t.getType(i);
    	}

    	return getSize(data, l, type);
        }
    static void writeData(DataOutput out, Object data[],
			  Table t) throws IOException {
	int len = t.getInternalColumnCount();
	int type[] = new int[len];

	for (int i = 0; i < len; i++) {
	    type[i] = t.getType(i);
	}

	writeData(out, len, type, data);
  }

    private static int getSize(Object data[], int l, int type[]) {
	int s = 0;

	for (int i = 0; i < l; i++) {
	    Object o = data[i];

	    s += 4;    // type

	    if (o != null) {
		switch (type[i]) {

		case INTEGER:
		    s += 4;

		    break;

		case FLOAT:

		case DOUBLE:
		    s += 8;

		    break;

		case BINARY:
		case VARBINARY:
		case LONGVARBINARY:
		case OTHER:
			s += getUTFsize("**"); //new format flag
			s += 4;
			s += ((ByteArray)o).byteValue().length;

			break;

		default:
		    s += getUTFsize(o.toString());
		}
	    }
	}

	return s;
    }
    private static int getUTFsize(String s) {
    	if (s == null) {
    	    s = "";
    	}
    	int len = s.length();
    	int l = 2;
    	for (int i = 0; i < len; i++) {
    	    int c = s.charAt(i);
    	    if ((c >= 0x0001) && (c <= 0x007F)) {
    		l++;
    	    } else if (c > 0x07FF) {
    		l += 3;
    	    } else {
    		l += 2;
    	    }
    	}

    	return l;
        }
	public Column(String name, int type, boolean nullable) {
		this.name = name;
		this.nullable = nullable;
		this.type = type;

	}

	public static int getTypeNr(String type) throws SQLException {
		Integer i = (Integer) types.get(type);
		if (i == null) {
			throw new SQLException("错误的数据类型");
		}
		return i.intValue();
	}

	public static String getType(int type) throws SQLException {
		switch (type) {

		case NULL:
			return "NULL";

		case INTEGER:
			return "INTEGER";

		case DOUBLE:
			return "DOUBLE";

		case VARCHAR_IGNORECASE:
			return "VARCHAR_IGNORECASE";

		case VARCHAR:
			return "VARCHAR";

		case CHAR:
			return "CHAR";

		case LONGVARCHAR:
			return "LONGVARCHAR";

		case DATE:
			return "DATE";

		case TIME:
			return "TIME";

		case DECIMAL:
			return "DECIMAL";

		case BIT:
			return "BIT";

		case TINYINT:
			return "TINYINT";

		case SMALLINT:
			return "SMALLINT";

		case BIGINT:
			return "BIGINT";

		case REAL:
			return "REAL";

		case FLOAT:
			return "FLOAT";

		case NUMERIC:
			return "NUMERIC";

		case TIMESTAMP:
			return "TIMESTAMP";

		case BINARY:
			return "BINARY";

		case VARBINARY:
			return "VARBINARY";

		case LONGVARBINARY:
			return "LONGVARBINARY";

		case OTHER:
			return "OBJECT";

		default:
			throw new SQLException("错误的数据类型");
		}
	}

	public boolean isNullable() {
		return nullable;
	}

	public static int compare(Object a, Object b, int type) throws SQLException {
		int i = 0;
		if (a == null) {
			if (b == null) {
				return 0;
			}
			return -1;
		}
		if (b == null) {
			return 1;
		}
		switch (type) {
		case NULL:
			return 0;
		case INTEGER:
			int ai = ((Integer) a).intValue();
			int bi = ((Integer) b).intValue();
			return (ai > bi) ? 1 : (bi > ai ? -1 : 0);
		case FLOAT:
		case DOUBLE:
			double ad = ((Double) a).doubleValue();
			double bd = ((Double) b).doubleValue();
			return (ad > bd) ? 1 : (bd > ad ? -1 : 0);
		case VARCHAR:
		case LONGVARCHAR:
			i = ((String) a).compareTo((String) b);
			break;
		case CHAR:
			i = StringUtil.rtrim((String) a).compareTo(
					StringUtil.rtrim((String) b));
			break;
		case VARCHAR_IGNORECASE:
			i = ((String) a).compareToIgnoreCase((String) b);
			break;
		case DATE:
			if (((java.sql.Date) a).after((java.sql.Date) b)) {
				return 1;
			} else if (((java.sql.Date) a).before((java.sql.Date) b)) {
				return -1;
			} else {
				return 0;
			}
		case TIME:
			if (((Time) a).after((Time) b)) {
				return 1;
			} else if (((Time) a).before((Time) b)) {
				return -1;
			} else {
				return 0;
			}
		case TIMESTAMP:
			if (((Timestamp) a).after((Timestamp) b)) {
				return 1;
			} else if (((Timestamp) a).before((Timestamp) b)) {
				return -1;
			} else {
				return 0;
			}
		case NUMERIC:
		case DECIMAL:
			i = ((BigDecimal) a).compareTo((BigDecimal) b);
			break;
		case BIT:
			boolean boola = ((Boolean) a).booleanValue();
			boolean boolb = ((Boolean) b).booleanValue();
			return (boola == boolb) ? 0 : (boolb ? -1 : 1);
		case TINYINT:
		case SMALLINT:
			short shorta = ((Short) a).shortValue();
			short shortb = ((Short) b).shortValue();
			return (shorta > shortb) ? 1 : (shortb > shorta ? -1 : 0);
		case BIGINT:
			long longa = ((Long) a).longValue();
			long longb = ((Long) b).longValue();
			return (longa > longb) ? 1 : (longb > longa ? -1 : 0);
		case REAL:
			float floata = ((Float) a).floatValue();
			float floatb = ((Float) b).floatValue();
			return (floata > floatb) ? 1 : (floatb > floata ? -1 : 0);
		case BINARY:
		case VARBINARY:
		case LONGVARBINARY:
		case OTHER:
			i = ((ByteArray) a).compareTo((ByteArray) b);
			break;
		default:
			throw new SQLException("不支持的方法! ");
		}
		return (i > 0) ? 1 : (i < 0 ? -1 : 0);
	}

	public static Object[] readData(DataInput in, int l) throws IOException,
			SQLException {
		Object data[] = new Object[l];
		for (int i = 0; i < l; i++) {
			int type = in.readInt();
			Object o = null;
			switch (type) {
			case NULL:
				o = null;
				break;
			case INTEGER:
				o = new Integer(in.readInt());
				break;
			case FLOAT:
			case DOUBLE:
				o = new Double(Double.longBitsToDouble(in.readLong()));
				break;
			case VARCHAR_IGNORECASE:
			case VARCHAR:
			case CHAR:
			case LONGVARCHAR:
				o = in.readUTF();
				break;
			case DATE:
				o = java.sql.Date.valueOf(in.readUTF());
				break;
			case TIME:
				o = Time.valueOf(in.readUTF());
				break;
			case TIMESTAMP:
				o = Timestamp.valueOf(in.readUTF());
				break;
			case NUMERIC:
			case DECIMAL:
				o = new BigDecimal(in.readUTF());
				break;
			case BIT:
				o = new Boolean(in.readUTF());
				break;
			case TINYINT:
			case SMALLINT:
				o = new Short(in.readUTF());
				break;
			case BIGINT:
				o = new Long(in.readUTF());
				break;
			case REAL:
				o = new Float(in.readUTF());
				break;
			case BINARY:
			case VARBINARY:
			case LONGVARBINARY:
			case OTHER:
				String str = in.readUTF();
				if (str.equals("**")) {
					int len = in.readInt();
					byte[] b = new byte[len];
					in.readFully(b);
					o = new ByteArray(b);
				} else {
					o = new ByteArray(str);
				}
				break;
			default:
				throw new SQLException("不支持的方法 !");
			}
			data[i] = o;
		}
		return data;
	}

	
	public static void writeData(DataOutput out, int l, int type[],
			Object data[]) throws IOException {
		for (int i = 0; i < l; i++) {
			Object o = data[i];
			if (o == null) {
				out.writeInt(NULL);
			} else {
				int t = type[i];
				out.writeInt(t);
				switch (t) {
				case INTEGER:
					out.writeInt(((Integer) o).intValue());
					break;
				case FLOAT:
				case DOUBLE:
					out.writeLong(Double.doubleToLongBits(((Double) o)
							.doubleValue()));
					break;
				case BINARY:
				case VARBINARY:
				case LONGVARBINARY:
				case OTHER:
					out.writeUTF("**");
					byte[] b = ((ByteArray) o).byteValue();
					out.writeInt(b.length);
					out.write(b, 0, b.length);
					break;
				default:
					out.writeUTF(o.toString());
					break;
				}
			}
		}
	}
}
